package com.hmdp.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.service.IVoucherOrderService;
import com.hmdp.service.impl.VoucherOrderServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Optional;

// 补偿任务：每5秒扫描Redis兜底队列，MQ恢复后补发
@EnableScheduling
@Slf4j
@Component
public class MqCompensateTask {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    @Autowired
    private IVoucherOrderService voucherOrderService;

    // 兜底队列Key
    private static final String BACKUP_KEY = "seckill:mq:backup";

    // 每5秒执行一次（可根据流量调整）
    @Scheduled(fixedRate = 5000)
    public void compensateMqMessage() {
        // 1. 先检查MQ是否恢复（简单判断：发送测试消息/检查Namesrv连接）
        if (!isMqAvailable()) {
            log.warn("MQ未恢复，暂不补发兜底订单");
            return;
        }

        // 2. 扫描Redis兜底队列，逐条补发
        String orderJson = stringRedisTemplate.opsForList().rightPop(BACKUP_KEY);
        while (orderJson != null) {
            VoucherOrder voucherOrder = JSONUtil.toBean(orderJson, VoucherOrder.class);
            Long orderId = voucherOrder.getId();
            // 3. 幂等校验：订单已存在则跳过（避免重复补发）
            if (voucherOrderService.getById(orderId) != null) {
                log.info("订单{}已存在，跳过补发", orderId);
                orderJson = stringRedisTemplate.opsForList().rightPop(BACKUP_KEY);
                continue;
            }
            // 4. 补发MQ消息
            try {
                rocketMQTemplate.convertAndSend(VoucherOrderServiceImpl.SECKILL_ORDER_TOPIC, voucherOrder);
                log.info("订单{}从Redis兜底队列补发MQ成功", orderId);
            } catch (Exception e) {
                // 补发失败：重新塞回队列（最多重试3次）
                Integer retryCount = Optional.ofNullable(voucherOrder.getRetryCount()).orElse(0);
                if (retryCount < 3) {
                    voucherOrder.setRetryCount(retryCount + 1);
                    stringRedisTemplate.opsForList().leftPush(BACKUP_KEY, JSONUtil.toJsonStr(voucherOrder));
                    log.warn("订单{}补发MQ失败，重试次数+1，当前：{}", orderId, retryCount + 1);
                } else {
                    // 重试3次失败：写入本地文件+告警
                    saveToLocalFile(orderId, voucherOrder);
                    sendAlarm("订单" + orderId + "补发MQ失败（3次重试），请人工处理");
                }
            }

            orderJson = stringRedisTemplate.opsForList().rightPop(BACKUP_KEY);
        }

        // 5. 扫描本地文件兜底（可选：处理Redis也故障的极端场景）
        compensateLocalFileMessage();
    }

    // 简易判断MQ是否可用：发送测试消息
    private boolean isMqAvailable() {
        try {
            rocketMQTemplate.convertAndSend("seckill:test", "test");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    // 补偿本地文件中的订单（略，逻辑同Redis）
    private void compensateLocalFileMessage() {
        // 读取本地文件 → 幂等校验 → 补发MQ → 删除已处理行
    }
    // 本地文件兜底（最后一道防线）
    private void saveToLocalFile(Long orderId, VoucherOrder voucherOrder) {
        // 按日期分片存储，避免单个文件过大
        String dateDir = DateUtil.today();
        String fileName = String.format("seckill_backup_%s.log", dateDir);
        String filePath = String.format("/data/seckill/backup/%s", fileName);

        // 确保目录存在
        File dir = new File("/data/seckill/backup/");
        if (!dir.exists()) {
            dir.mkdirs();
        }

        // 追加写入文件（BufferedWriter提升性能）
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath, true))) {
            String orderJson = JSONUtil.toJsonStr(voucherOrder);
            writer.write(orderJson);
            writer.newLine();
            log.info("订单{}已写入本地兜底文件：{}", orderId, filePath);
        } catch (IOException e) {
            log.error("订单{}写入本地文件失败", orderId, e);
            // 触发告警：钉钉/企业微信通知运维
            sendAlarm("秒杀订单" + orderId + "本地兜底失败，可能丢失！");
        }
    }

    private void sendAlarm(String alarmMsg) {
        // 1. 先打印错误日志（必做）
        log.error("秒杀系统告警：{}", alarmMsg);
    }
}
